/*
 * Copyright (c) 2000
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Written by Tom Henderson
 * Version 1.2:  August 2000
 */

#ifdef STP
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/stat.h>

#include <net/if.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/stp_var.h>
#include <netinet/stp.h>

/* 
 * Process a STP user request.  If this is a send request
 * then m is the mbuf chain of send data.  
 * m is an mbuf chain of data, nam is a network address mbuf, and control
 * is a control mbuf.
 */
int
stp_usrreq(so, req, m, nam, control)
	struct socket *so;
	int req;
	struct mbuf *m, *nam, *control;
{
	register struct inpcb *inp;
	register struct stpcb *sp;
	int s;
	int error = 0;
	int sendbytes;

	/* PRU_CONTROL request (from the ioctl() call) is sent to in_control()*/
	if (req == PRU_CONTROL)
		return (in_control(so, (u_long)m, (caddr_t)nam,
			(struct ifnet *)control));

	/* Blocks control information sent along with sendmsg(invalid for STP)*/
	if (control && control->m_len) {
		m_freem(control);
		if (m && req != PRU_SENSE && req != PRU_RCVOOB)
			m_freem(m);
		return (EINVAL);
	}

	/* Locks at splnet */
	s = splnet();
	inp = sotoinpcb(so);
	/*
	 * When a STP is attached to a socket, then there will be
	 * a (struct inpcb) pointed at by the socket, and this
	 * structure will point at a subsidary (struct stpcb).
	 */
	if (inp == 0 && req != PRU_ATTACH) {
		splx(s);
		if (m && req != PRU_SENSE && req != PRU_RCVOOB)
			m_freem(m);
		return (ENOTCONN);
	}
	if (inp) 
		sp = intostpcb(inp); 	/* pointer to stp pcb now obtained */

	switch (req) {
	/*
	 * STP attaches to socket via PRU_ATTACH, reserving space,
	 * and an internet control block.
	 */
	case PRU_ATTACH:
		if (inp) {
			error = EISCONN;  /* Socket already points to a PCB */
			break;
		}
		error = stp_attach(so);
		if (error)
			break;
		if ((so->so_options & SO_LINGER) && so->so_linger == 0)
			so->so_linger = STP_LINGERTIME;
		sp = sotostpcb(so);  /* sp now points to the socket's STP PCB */
		break;

	/*
	 * PRU_DETACH detaches the STP protocol from the socket.
	 * The close() system call issues PRU_DETACH if PRU_DISCONNECT fails
	 */
	case PRU_DETACH:
		if (sp->s_state <= STP_LISTEN) {/* Simply drops PCB */
			sp = stp_close(sp);
		} else if ((so->so_options & SO_LINGER) && so->so_linger == 0)
			sp = stp_drop(sp, 0); /* Hard disconnect */
		else {
			soisdisconnecting(so);
			sbflush(&so->so_rcv);
			stp_disconnect(sp);
			sp->disc_pending = TRUE;
		}
		break;

	/*
	 * Give the socket an address.
	 */
	case PRU_BIND:
		ASSERT(sp->s_state == STP_CLOSED);
		error = in_pcbbind(inp, nam);
		if (error)
			break;
		break;

	/*
	 * Prepare to accept connections.
	 */
	case PRU_LISTEN:
		ASSERT(sp->s_state == STP_CLOSED);
		if (inp->inp_lport == 0)
			error = in_pcbbind(inp, (struct mbuf *)0);
		if (error == 0) {
			sp->s_state = STP_LISTEN;
		}
		break;

	/*
	 * Initiate connection to peer.
	 * Create a template for use in transmissions on this connection.
	 * Enter BGN_SENT state, and mark socket as connecting.
	 * Start connection control timer, and seed output sequence space.
	 * Send initial segment on connection.
	 */
	case PRU_CONNECT:
		ASSERT(sp->s_state == STP_CLOSED);
		/* Assign ephemeral port */
		if (inp->inp_lport == 0) {
			error = in_pcbbind(inp, (struct mbuf *)0);
			if (error)
				break;
		}
		/* 
		 * Acquire route to destination, determine outgoing interface,
		 * and verify that the outgoing interface is unique
		 */
		error = in_pcbconnect(inp, nam);
		if (error)
			break;

		/* initialize IP and STP headers */
		sp->s_template = stp_template(sp);
		if (sp->s_template == 0) {
			in_pcbdisconnect(inp);
			error = ENOBUFS;
			break;
		}

		/* set the appropriate bits in the socket's state variable */
		soisconnecting(so);

		sp->cc_count = 1;
		error = stp_sndcc_open(sp, STP_PDU_BGN);
		/* 
		 * Start connection control timer.  CC should be retransmitted 
		 * if no response within 2* srtt, or INITIAL_RTT if srtt == 0.
		 */
		ASSERT(!sp->s_timer); 	/* No timer should be running */
		stp_start_timer(sp, STP_TIMER_BGN, (sp->s_srtt ? 
		    ( 2 * (sp->s_srtt >> SRTT_SHIFT)/MS_PER_TICK ) : 
	    	    INITIAL_SRTT/MS_PER_TICK) ); 
		sp->s_state = STP_BGN_SENT;
		break;

	/* This call, a result of socketpair system call, is not supported. */
	case PRU_CONNECT2:
		error = EOPNOTSUPP;
		break;

	/*
	 * Initiate disconnect from peer.
	 * If connection never passed embryonic stage, just drop;
	 * else if don't need to let data drain, then can just drop anyways,
	 * else have to begin STP shutdown process: mark socket disconnecting,
	 * drain unread data, state switch to reflect user close, and
	 * send segment (e.g. END) to peer.  Socket will be really disconnected
	 * when peer sends END and acks ours.
	 *
	 *  XXX Socket that encounters an error should be reused, but isn't
	 */
	case PRU_DISCONNECT:
		if (sp->s_state <= STP_LISTEN) {/* Simply drops PCB */
			sp = stp_close(sp);
		} else if ((so->so_options & SO_LINGER) && so->so_linger == 0)
			sp = stp_drop(sp, 0); /* Hard disconnect */
		else {
			soisdisconnecting(so);
			sbflush(&so->so_rcv);
			stp_disconnect(sp);
			sp->disc_pending = TRUE;
		}
		break;

	/*
	 * Accept a connection.  Essentially all the work is
	 * done at higher levels; just return the address
	 * of the peer, storing through addr.
	 */
	case PRU_ACCEPT:
		ASSERT(sp->s_state == STP_EST);
		in_setpeeraddr(inp, nam);
		break;

	/*
	 * Mark the connection as being incapable of further output.
	 * If there is still data to be sent, send it before sending the END
	 */
	case PRU_SHUTDOWN:
		socantsendmore(so);
		stp_shutdown(sp);
		sp->shutdown_pending = TRUE;
		break;

	/* After a receive, do nothing. */
	case PRU_RCVD:
		break;
	
	/* Do not support sending OOB before the connection is set up!  */
	case PRU_SENDOOB:
		if (sbspace(&so->so_snd) < -512) {
			m_freem(m);
			error = ENOBUFS;
			break;
		}
		sbappend(&so->so_snd, m);
		sp->snd_up = so->so_snd.sb_cc;
		stp_set_output(sp);
		break;

	/*
	 * Do a send by putting data in output queue and updating urgent
	 * marker if URG set.  Possibly send more data.
	 */
	case PRU_SEND_EOF:
		sp->shutdown_pending = TRUE;
	case PRU_SEND:
		/* Append data to send buffer */
		sendbytes = so->so_snd.sb_cc;
		sbappend(&so->so_snd, m);
		sendbytes = so->so_snd.sb_cc - sendbytes;
		if (nam && sp->s_state <= STP_LISTEN) {
			/* Do implied connect if not yet connected */
			/* Assign ephemeral port */
			if (inp->inp_lport == 0) {
				error = in_pcbbind(inp, (struct mbuf *)0);
				if (error)
					break;
        		} 
        		/* 
			 * Acquire route to destination, determine outgoing 
			 * interface, and verify that the outgoing interface 
			 * is unique
         		 */
        		error = in_pcbconnect(inp, nam);
        		if (error)
            			break;
     
        		/* initialize IP and STP headers */
        		sp->s_template = stp_template(sp);
        		if (sp->s_template == 0) {
				in_pcbdisconnect(inp);
				error = ENOBUFS;
				break;
        		}
            
        		/* 
			 * set the appropriate bits in the socket's state 
			 * variable 
			 */
        		soisconnecting(so);
        		sp->cc_count = 1;
        		error = stp_sndcc_open(sp, (STP_PDU_BGN | STP_PDU_SD));
        		/*
         	 	 * Start connection control timer.  CC should be 
			 * retransmitted if no response within 2* srtt, or 
			 * INITIAL_SRTT if srtt == 0.
         	 	 */
        		ASSERT(!sp->s_timer);   /* No timer should be running */
			stp_start_timer(sp, STP_TIMER_BGN, (sp->s_srtt ?
			    ( 2 * (sp->s_srtt >> SRTT_SHIFT)/MS_PER_TICK ) : 
		    	    INITIAL_SRTT/MS_PER_TICK) );
   
			if (req == PRU_SEND_EOF) {
				sp->s_state = STP_BGN_DATA_END_SENT;
				socantsendmore(so);
			} else
				sp->s_state = STP_BGN_DATA_SENT;
        		break;
		} else {
			/*
 			 * BGN has been sent. Call set_output to stimulate 
			 * output.  
			 */
			stp_set_output(sp);
		}
		break;

	/*
	 * Abort the STP.
	 */
	case PRU_ABORT:
		sp = stp_drop(sp, ECONNABORTED);
		break;
	
	/* this returns the size of the send buffer as the st_blksize element */
	case PRU_SENSE:
		((struct stat *) m)->st_blksize = so->so_snd.sb_hiwat;
		splx(s);
		return (0);

	case PRU_RCVOOB:
		if ((so->so_oobmark == 0 && 
		    (so->so_state & SS_RCVATMARK) == 0) ||
		    so->so_options & SO_OOBINLINE ||
		    sp->s_oobflags & STPOOB_HADDATA) {
			error = EINVAL;
			break;
		}
		if ((sp->s_oobflags & STPOOB_HAVEDATA) == 0) {
			error = EWOULDBLOCK;
			break;
		}
		m->m_len = 1;
		*mtod(m, caddr_t) = sp->s_iobc;
		if (((int)nam & MSG_PEEK) == 0)
			sp->s_oobflags ^= (STPOOB_HAVEDATA | STPOOB_HADDATA);
		break;

	case PRU_SOCKADDR:
		in_setsockaddr(inp, nam);
		break;
	case PRU_PEERADDR:
		in_setpeeraddr(inp, nam);
		break;

	case PRU_SLOWTIMO:
		break;

	default:
		panic("stp_usrreq");
	}
	splx(s);
	return (error);
}

/* 
 * Called by getsockopt and setsockopt when the descriptor argument refers to
 * an STP socket and when the level is not SOL_SOCKET.
 * Two socket options currently supported:  STP_NODELAY, and STP_MAXSEG.
 */
int
stp_ctloutput(op, so, level, optname, mp)
	int op;
	struct socket *so;
	int level, optname;
	struct mbuf **mp;
{
	int error = 0, s;
	struct inpcb *inp;
	register struct stpcb *sp;
	register struct mbuf *m;
	register int i;

	s = splnet();
	inp = sotoinpcb(so);
	if (inp == NULL) {
		splx(s);
		if (op == PRCO_SETOPT && *mp)
			(void) m_free(*mp);
		return (ECONNRESET);
	}
	if (level != IPPROTO_STP) {
		error = ip_ctloutput(op, so, level, optname, mp);
		splx(s);
		return (error);
	}
	sp = intostpcb(inp);

	switch (op) {

	case PRCO_SETOPT:
		m = *mp;
		switch (optname) {
		case STP_NODELAY:
			if (m == NULL || m->m_len < sizeof(int))
				error = EINVAL;
			else if (*mtod(m, int *))
				/* 
				 * if integer value is nonzero, set 
				 * nodelay = TRUE
				 */
				sp->stp_nodelay = TRUE;
			else
				sp->stp_nodelay = FALSE;
			break;

		case STP_MAXSEG:
			if (m && (i = *mtod(m, int *)) > 0 && i <= STP_MSS)
				sp->s_maxseg = i;
			else
				error = EINVAL;
			break;

		default:
			error = ENOPROTOOPT;
			break;
		}
		if (m)
			(void) m_free(m);
		break;

	case PRCO_GETOPT:
		*mp = m = m_get(M_WAIT, MT_SOOPTS);
		m->m_len = sizeof(int);

		switch (optname) {

		case STP_NODELAY:
			/* returns current value of stp_nodelay */
			*mtod(m, int *) = sp->stp_nodelay;
			break;
		case STP_MAXSEG:
			*mtod(m, int *) = sp->s_maxseg;
			break;
		default:
			error = ENOPROTOOPT;
			break;
		}
		break;
	}
	splx(s);
	return (error);
}

/*
 * Attach STP protocol to socket, allocating
 * internet protocol control block, tcp control block,
 * bufer space, and entering LISTEN state if to accept connections.
 */
int
stp_attach(so)
	struct socket *so;
{
	register struct stpcb *sp;
	struct inpcb *inp;
	int error;
	
	/* Allocate space for send and receive buffer */
	if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) {
		error = soreserve(so, (u_long)stp_sendspace,
		    (u_long)stp_recvspace);
		if (error)
			return (error);
	}
	error = in_pcballoc(so, &scbinfo);
	if (error)
		return (error);
	inp = sotoinpcb(so);
	sp = stp_newstpcb(inp);
	if (sp == 0) {
		int nofd = so->so_state & SS_NOFDREF;	/* XXX */
		so->so_state &= ~SS_NOFDREF;	/* don't free the socket yet */
		in_pcbdetach(inp);
		so->so_state |= nofd;
		return (ENOBUFS);
	}
	sp->s_state = STP_CLOSED;
	return (0);
}

/* 
 * This function is called when a STAT is received and the 
 * sp->shutdown_pending flag is set, or when a PRU_SHUTDOWN signal
 * is received.  If the sender has received acks for all of its data,
 * the write side of the connection is closed by sending an END.
 */
void
stp_shutdown(sp)
	register struct stpcb *sp;
{
	struct socket *so = sp->s_inpcb->inp_socket;

	/* If all done sending data, send END and change state */
	if ((sp->snd_nxt == sp->snd_una) && (so->so_snd.sb_cc == 0) && 
		(sp->s_state > STP_LISTEN)) {
		switch (sp->s_state) {
		case STP_EST:
		case STP_CLOSE_RECV:
			/* 
			 * Start cc timer, but first kill any pending 
			 * timers
			 */
			stp_killtimers(sp);
			stp_start_timer(sp, STP_TIMER_END, (sp->s_srtt ?
			    ( 2 * (sp->s_srtt >> SRTT_SHIFT)/MS_PER_TICK) : 
			    INITIAL_SRTT/MS_PER_TICK) ); 
		 	stp_sndcc_close(sp, STP_PDU_END);
			sp->cc_count = 1;
			if (sp->s_state == STP_EST)
				sp->s_state = STP_HALF_CLOSE_PENDING;
			else
				sp->s_state = STP_LAST_ACK;
			break;

		case STP_HALF_CLOSE_PENDING:
		case STP_HALF_CLOSED:
		case STP_BGN_SENT:
		case STP_BGN_DATA_SENT:
		case STP_BGN_DATA_END_SENT:
		case STP_FULL_CLOSE_PENDING:
		case STP_LAST_ACK:
			DBG(printf("Illegal state for shutdown: %x\n", sp->s_state););
			break;
		default:
			break;
		}
	}
}

/* 
 * This function is called when a STAT is received and the 
 * sp->disc_pending flag is set, or when a PRU_DISCONNECT
 * or PRU_DETACH signal is received.  If the sender has received acks for 
 * all of its data, the write side of the connection is closed by sending 
 * an END.
 */
void
stp_disconnect(sp)
	register struct stpcb *sp;
{
	struct socket *so = sp->s_inpcb->inp_socket;

	/*
	 * If connection is done sending data, move connection to next state
	 * and send END.  If not yet, the disconnect request has been cached.
 	 */
	if ((sp->snd_nxt == sp->snd_una) && (so->so_snd.sb_cc == 0) && 
	    (sp->s_state >= STP_EST)) {
		/* Start cc timer, but first kill any pending timers */
		if (sp->s_state == STP_EST || sp->s_state == STP_CLOSE_RECV) {
			stp_killtimers(sp);
		 	stp_sndcc_close(sp, STP_PDU_END);
			sp->cc_count = 1;
			stp_start_timer(sp, STP_TIMER_END, (sp->s_srtt ?
			    (2 * (sp->s_srtt >> SRTT_SHIFT)/MS_PER_TICK) : 
			    INITIAL_SRTT/MS_PER_TICK) ); 
		}
		/* Move connection to next state */
		switch (sp->s_state) {
		case STP_EST:
		case STP_HALF_CLOSE_PENDING:
			sp->s_state = STP_FULL_CLOSE_PENDING;
			break;
		case STP_CLOSE_RECV:
			sp->s_state = STP_LAST_ACK;
			break;
		default:
			break;
		}
	}
}


/*
 * Sysctl for stp variables.  Does nothing.
 */
int
stp_sysctl(name, namelen, oldp, oldlenp, newp, newlen)
	int *name;
	u_int namelen;
	void *oldp;
	size_t *oldlenp;
	void *newp;
	size_t newlen;
{
	return 0;
}
#endif
